package net.gdface.jmx;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.DynamicMBean;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.ReflectionException;

import net.gdface.utils.BeanPropertyUtils;
import net.gdface.utils.SimpleLog;
/**
 * 实现接口的JMX{@link DynamicMBean}<br>
 * @author guyadong
 *
 */
public class DynamicMBeanDecorator implements DynamicMBean{
    private Object delegate;
    private final MBeanInfo mBeanInfo;
    private final Map<String, List<Method>> invokeMethods = new HashMap<>();
    private final Class<?> interfaceClass;
	private final Map<String, PropertyDescriptor> properties = new HashMap<>();
	private boolean logError = false;
	private boolean trace = false;
    /**
     * 构造方法
     * @param mBeanInfo MBean 描述对象
     */
    public DynamicMBeanDecorator(MBeanInfo mBeanInfo) {
    	if(mBeanInfo == null){
    		throw new NullPointerException("mBeanInfo is null");
    	}
        try {
			interfaceClass = Class.forName(mBeanInfo.getClassName());
			if(!interfaceClass.isInterface()){
				throw new IllegalArgumentException(String.format("%s is not interface class",mBeanInfo.getClassName()));
			}
			this.mBeanInfo = mBeanInfo;
			// 为接口定义类创建方法名--调用方法的映射
	    	for(Method method : interfaceClass.getDeclaredMethods()){
				try {
					addMethod(invokeMethods, interfaceClass, method.getName(), method.getParameterTypes());
				} catch (NoSuchMethodException e) {
					throw new RuntimeException(e);
				}
			}
	    	properties.putAll(BeanPropertyUtils.getProperties(interfaceClass, 0, true));
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
    }
    /**
     * 构造方法
     * @param mBeanInfo MBean 描述对象
     * @param delegate 代理的 T 实例
     */
    public DynamicMBeanDecorator(MBeanInfo mBeanInfo,Object delegate) {
    	this(mBeanInfo);
    	delegate(delegate);
    }
    /**
     * @return 返回被装饰的接口实例
     */
    public Object delegate() {
        return delegate;
    }
    
    /**
     * 指定代理的接口实例
     * @param delegate 代理实例,代理实现可以
     * @return 当前对象
     */
    public DynamicMBeanDecorator delegate(Object delegate) {
        if(null == delegate){
            return this;
        }
    	Class<?> delegateClass = delegate.getClass();
        if(!interfaceClass.isAssignableFrom(delegateClass)){
        	invokeMethods.clear();
        	// 检查 delegate 是否实现了所有接口定义方法,如果有未实现方法则抛出异常
        	for(Method method : interfaceClass.getDeclaredMethods()){
        		try {
					addMethod(invokeMethods, delegateClass, method.getName(), method.getParameterTypes());
				} catch (NoSuchMethodException e) {
					String msg = String.format("delegate %s is not a implementation of %s,and it don't  implement method %s of %s", 
							delegateClass.getName(),
							interfaceClass.getName(),
							method.toString(),
							interfaceClass.getName());
		        	throw new IllegalArgumentException(msg, e);
				}
        	}
        	Map<String, PropertyDescriptor> delegateProperties = BeanPropertyUtils.getProperties(delegateClass, 0, true);
        	// 检查 delegate 是否包含了所有接口定义方法定义的属性
        	if(!delegateProperties.keySet().containsAll(properties.keySet())){
        		String msg = String.format("delegate %s properties is not contains all properties of %s", 
						delegateClass.getName(),
						interfaceClass.getName());
	        	throw new IllegalArgumentException(msg);
        	}
        	// 用 delegate 中的属性对象替换原接口方法中的属性对象 PropertyDescriptor
        	for(String name:properties.keySet()){
        		properties.put(name, delegateProperties.get(name));
        	}
        }
        this.delegate = delegate;
        return this;
    }

    private Object delegateChecked(){
		if(delegate == null){
			throw new NullPointerException("delegate is null");
		}
		return delegate;
	}
	private void checkDelegateNotNull(){
		if(delegate == null){
			throw new NullPointerException("delegate is null");
		}
	}
	@Override
    public MBeanInfo getMBeanInfo() {
        return mBeanInfo;
    }

    @Override
    public Object getAttribute(String attribute)
            throws AttributeNotFoundException, MBeanException, ReflectionException {
    	PropertyDescriptor descriptor = properties.get(attribute);    	
    	if(descriptor == null){
    		throw new AttributeNotFoundException("INVALID attribute " + attribute);
    	}
    	Method getter = descriptor.getReadMethod();    	
    	if(getter == null){
    		throw new AttributeNotFoundException("unreadable attribute " + attribute);
    	}
    	try {
			return getter.invoke(delegateChecked());
		} catch (IllegalArgumentException | IllegalAccessException e) {
			throw new ReflectionException(e);
		} catch (InvocationTargetException e) {
			unwrapInvocationTargetException(e);
			throw new RuntimeException(e); // not reached
		} catch (RuntimeException e) {
			throw new MBeanException(e);
		}
    }

    @Override
    public void setAttribute(Attribute attribute)
            throws AttributeNotFoundException, InvalidAttributeValueException, MBeanException, ReflectionException {
    	PropertyDescriptor descriptor = properties.get(attribute.getName());    	
    	if(descriptor == null){
    		throw new AttributeNotFoundException("INVALID attribute " + attribute.getName());
    	}
    	Method setter = descriptor.getWriteMethod();    	
    	if(setter == null){
    		throw new AttributeNotFoundException("unwriteable attribute " + attribute.getName());
    	}
    	try {
			setter.invoke(delegateChecked(),attribute.getValue());
		} catch (IllegalArgumentException | IllegalAccessException e) {
			throw new ReflectionException(e);
		} catch (InvocationTargetException e) {
			unwrapInvocationTargetException(e);
		} catch (RuntimeException e) {
			throw new MBeanException(e);
		}
    }
    @Override
    public AttributeList getAttributes(String[] attributes) {
        final AttributeList result = new AttributeList(attributes.length);
        for (String attrName : attributes) {
            try {
                final Object attrValue = getAttribute(attrName);
                result.add(new Attribute(attrName, attrValue));
            } catch (Exception e) {
                // OK: attribute is not included in returned list, per spec
                // XXX: log the exception
            }
        }
        return result;
    }

    @Override
    public AttributeList setAttributes(AttributeList attributes) {
        final AttributeList result = new AttributeList(attributes.size());
        for (Object attrObj : attributes) {
            // We can't use AttributeList.asList because it has side-effects
            Attribute attr = (Attribute) attrObj;
            try {
                setAttribute(attr);
                result.add(new Attribute(attr.getName(), attr.getValue()));
            } catch (Exception e) {
                // OK: attribute is not included in returned list, per spec
                // XXX: log the exception
            }
        }
        return result;
    }
    
    private static void  unwrapInvocationTargetException(InvocationTargetException e)
			throws MBeanException {
		Throwable t = e.getCause();
		if (t instanceof RuntimeException){
			throw (RuntimeException) t;
		}else if (t instanceof Error){
			throw (Error) t;
		}else{
			throw new MBeanException((Exception) t,
					(t == null ? null : t.toString()));
		}
	}
	private static void addMethod(Map<String, List<Method>> methods,Class<?> clazz, String name, Class<?>... parameters) 
			throws NoSuchMethodException{
		Method m = clazz.getMethod(name, parameters);
		if(!methods.containsKey(name)){
			methods.put(name, new ArrayList<Method>());
		}
		methods.get(name).add(m);

    }
    private static final Map<Class<?>, Class<?>> PRIMITIVE_TYPE_MAP = new HashMap<Class<?>, Class<?>>() {
        private static final long serialVersionUID = 2638066380035384674L;
        {
            put(int.class, Integer.class);
            put(boolean.class, Boolean.class);
            put(byte.class, Byte.class);
            put(short.class, Short.class);
            put(char.class, Character.class);
            put(long.class, Long.class);
            put(float.class, Float.class);
            put(double.class, Double.class);
        }
    };
    /**
     * from对象能否则直接赋值给to
     * @param to
     * @param from
     * @return
     */
    private static final boolean isAssignable(Class<?> to,Class<?>from){
        if (from.isPrimitive()) {
            if (to != from && !to.isAssignableFrom(PRIMITIVE_TYPE_MAP.get(from)))
                return false;
        } else if (!to.isAssignableFrom(from))
            return false;
        return true;
    }
    /**
	 * from对象能否则直接赋值给to
	 * @param to
	 * @param from
	 * @return
	 */
	private static final boolean isAssignable(Class<?> to,Object from){
		if(from == null){
			return ! to.isPrimitive();
		}
	    return isAssignable(to,from.getClass());
	}

	/**
     * from对象是否能转换成to
     * @param to
     * @param from
     * @return
     */
    private static final boolean isConvert(Class<?> to, Object from) {
        if (!isAssignable(to, from)){
            if (to.isPrimitive() && from !=null && PRIMITIVE_TYPE_MAP.get(to) == from.getClass()) {
                return true;
            }
            return false;
        }
        return true;
    }
    
    /**
     * @param methods 待搜索的方法列表
     * @param name 方法名
     * @param parameterValues 希望匹配的参数值数组
     * @return
     * @throws NoSuchMethodException
     */
    private static Method getMethod(List<Method> input,String name, Object... parameterValues) throws NoSuchMethodException{
    	List<Method> methods = new ArrayList<>(input);
        // 查找同名且参数数目相同的所有方法
        for (Iterator<Method> itor = methods.iterator();itor.hasNext();){
        	Method m = itor.next();
            if (!m.getName().equals(name) || m.getParameterTypes().length != parameterValues.length){
                itor.remove();
            }
        }
        if(methods.size() ==1){
        	return methods.get(0);
        }else if (methods.size() > 1) {
            // 过滤掉所有不能匹配的方法        
            for (int i = 0; i < parameterValues.length; i++) {
                for (Iterator<Method> it = methods.iterator(); it.hasNext();) {
                    if (!isConvert(it.next().getParameterTypes()[i], parameterValues[i]))
                        it.remove();
                }
                if (methods.size() <= 1){
                    break;
                }
            }
            if (methods.size() > 1) {
                //如果还有多个方法满足条件，再过滤掉不能直接赋值的方法
                for (int i = 0; i < parameterValues.length; i++) {
                    for (Iterator<Method> it = methods.iterator(); it.hasNext();) {
                        if (!isAssignable(it.next().getParameterTypes()[i], parameterValues[i])){
                            it.remove();
                        }
                    }
                    if (methods.size() <= 1){
                        break;
                    }
                }
            }
            if (methods.size() == 1){
                return methods.iterator().next();
            }
            else if (methods.size() > 1){
                //如果还有多个方法满足条件，再过滤掉类型不相等的方法
                for (int i = 0; i < parameterValues.length; i++) {
                    for (Iterator<Method> it = methods.iterator(); it.hasNext();) {
                        if (it.next().getParameterTypes()[i]!= parameterValues[i])
                            it.remove();
                    }
                    if (methods.size() <= 1){
                        break;
                    }
                }
                if (methods.size() == 1){
                    return methods.iterator().next();
                }
                else    if (methods.size() > 1){
                    throw new IllegalStateException("found more matched method");
                }
            }
        }
        throw new NoSuchMethodException(name);
    }
    /**
     * @param clazz 待搜索的类
     * @param name 方法名
     * @param parameterValues 希望匹配的参数值数组
     * @return
     * @throws NoSuchMethodException
     */
    static Method getMethod(Class<?>clazz,String name, Object... parameterValues) throws NoSuchMethodException{
    	return getMethod(new ArrayList<>(Arrays.asList(clazz.getMethods())), name, parameterValues);
    }
    	
    @Override
    public Object invoke(String actionName, Object[] params, String[] signature)
            throws MBeanException, ReflectionException {

        try {
        	checkDelegateNotNull();
        	List<Method> methods = invokeMethods.get(actionName);
        	if(methods == null){
        		throw new NoSuchMethodException("not found interface method " + actionName);
        	}else if(methods.size() == 1){
        		return methods.get(0).invoke(delegate, params);
        	}
			return getMethod(methods, actionName, params).invoke(delegate, params);
		} catch (IllegalAccessException | NoSuchMethodException e) {
			throw new ReflectionException(e);
		} catch (InvocationTargetException e) {
			Throwable target = e.getTargetException();
			if(logError){
				SimpleLog.log("{}:{}",target.getClass().getName(),target.getMessage());
				if(trace){
					SimpleLog.log(target);
				}
			}
			if(target instanceof Exception){
				throw new MBeanException((Exception) target);
			}else{
				// 非 Exception 异常只能原样抛出
				throw new MBeanException(e);
			}
		} catch (RuntimeException e) {
			throw new ReflectionException(e);
		}
    }
	/**
	 * 设置日志参数
	 * @param logError 是否输出方法调用异常信息
	 * @param trace 是否输出方法调用异常堆栈,logError为{@code true}时有效
	 * @return 当前对象
	 */
	public DynamicMBeanDecorator setLogConfig(boolean logError,boolean trace) {
		this.logError = logError;
		this.trace = trace;
		return this;
	}
}